package pmt.plugin

import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.tasks.Copy
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.jetbrains.compose.ComposeExtension
import org.jetbrains.compose.ComposePlugin
import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

class ComposePlugin : Plugin<Project> {
    override fun apply(project: Project) {
        with(project) {
            val android = extensions.findByType(LibraryExtension::class)
            val composeExtension = extensions.findByType(ComposeExtension::class)
            val versionCatalogs = project.extensions.getByType(VersionCatalogsExtension::class)

            configureKotlinMultiplatformExtension(android, composeExtension, versionCatalogs)
            configureAndroidExtension(android, composeExtension, versionCatalogs)
            configureIosTesting()
        }
    }

    private fun Project.configureKotlinMultiplatformExtension(
        android: LibraryExtension?,
        composeExtension: ComposeExtension?,
        versionCatalogs: VersionCatalogsExtension
    ) {
        configure<KotlinMultiplatformExtension> {
            if (enableIos) {
                iosArm64()
                iosSimulatorArm64() // to run tests
            }

            if (android != null) {
                jvm("desktop")
                androidTarget()
                androidTarget {
                    @OptIn(ExperimentalKotlinGradlePluginApi::class)
                    instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test)
                    unitTestVariant.sourceSetTree.set(KotlinSourceSetTree.unitTest)
                }

                applyDefaultHierarchyTemplate {
                    common {
                        group("jvm") {
                            withJvm()
                            withAndroidTarget()
                        }
                        group("skiko") {
                            withJvm()
                            withNative()
                        }
                    }
                }
            } else {
                jvm()
                applyDefaultHierarchyTemplate()
            }

            compilerOptions {
                freeCompilerArgs.add("-Xexpect-actual-classes")
            }

            addCommonMainDependencies(composeExtension)
            addCommonTestDependencies(composeExtension)
            addDesktopMainDependencies(composeExtension)
            addAndroidTestDependencies(android, composeExtension, versionCatalogs)

            if (android != null) {
                configureCompileSdk()
                val androidMainSourceSetDir = projectDir.resolve("androidMain")
                val androidExtension = extensions.findByType(CommonExtension::class)
                if (androidExtension != null) {
                    androidExtension.sourceSets["main"].aidl.srcDirs(
                        androidMainSourceSetDir.resolve("aidl")
                    )
                    // add more sourceSet dirs if necessary.
                }
            }

            sourceSets.configureEach {
                val invalidSourceSetNames = setOf(
                    "androidAndroidTestRelease",
                    "androidTestFixtures",
                    "androidTestFixturesDebug",
                    "androidTestFixturesRelease"
                )
                if (this.name in invalidSourceSetNames) {
                    logger.info("Removing invalid source set: ${this.name}")
                    sourceSets.remove(this)
                }
            }
        }

        if (composeExtension != null) {
            tasks.named("generateComposeResClass") {
                dependsOn("generateResourceAccessorsForAndroidUnitTest")
            }
            tasks.withType(KotlinCompilationTask::class) {
                dependsOn(tasks.matching { it.name == "generateComposeResClass" })
                dependsOn(tasks.matching { it.name == "generateResourceAccessorsForAndroidRelease" })
                dependsOn(tasks.matching { it.name == "generateResourceAccessorsForAndroidUnitTest" })
                dependsOn(tasks.matching { it.name == "generateResourceAccessorsForAndroidUnitTestRelease" })
                dependsOn(tasks.matching { it.name == "generateResourceAccessorsForAndroidUnitTestDebug" })
                dependsOn(tasks.matching { it.name == "generateResourceAccessorsForAndroidDebug" })
            }
        }
    }

    private fun Project.addCommonMainDependencies(composeExtension: ComposeExtension?) {
        configure<KotlinMultiplatformExtension> {
            sourceSets.commonMain.dependencies {
                if (composeExtension != null) {
                    val compose = ComposePlugin.Dependencies(project)
                    api(compose.foundation)
                    api(compose.animation)
                    api(compose.ui)
                    api(compose.material3)
                    api(compose.materialIconsExtended)
                    api(compose.runtime)
                }

                if (project.path != ":utils:platform") {
                    implementation(project(":utils:platform"))
                }
            }
        }
    }

    private fun Project.addCommonTestDependencies(composeExtension: ComposeExtension?) {
        configure<KotlinMultiplatformExtension> {
            sourceSets.commonTest.dependencies {
                if (composeExtension != null) {
                    val compose = ComposePlugin.Dependencies(project)
                    @OptIn(ExperimentalComposeLibrary::class)
                    implementation(compose.uiTest)
                }
            }
        }
    }

    private fun Project.addDesktopMainDependencies(composeExtension: ComposeExtension?) {
        if (composeExtension != null) {
            configure<KotlinMultiplatformExtension> {
                sourceSets.getByName("desktopMain").dependencies {
                    val compose = ComposePlugin.Dependencies(project)
                    implementation(compose.desktop.uiTestJUnit4)
                }
            }
        }
    }

    private fun Project.addAndroidTestDependencies(
        android: LibraryExtension?,
        composeExtension: ComposeExtension?,
        versionCatalogs: VersionCatalogsExtension
    ) {
        if (android != null && composeExtension != null) {
            val composeVersion =
                versionCatalogs.named("libs").findVersion("jetpack-compose").get().toString()

            configure<KotlinMultiplatformExtension> {
                listOf(
                    sourceSets.getByName("androidInstrumentedTest"),
                    sourceSets.getByName("androidUnitTest"),
                ).forEach { sourceSet ->
                    sourceSet.dependencies {
                        implementation("androidx.compose.ui:ui-test-junit4-android:$composeVersion")
                        implementation("androidx.compose.ui:ui-test-manifest:$composeVersion")
                        // TODO: this may cause dependency rejection when importing the project in IntelliJ.
                    }
                }
            }


            dependencies {
                "debugImplementation"("androidx.compose.ui:ui-test-manifest:$composeVersion")
            }
        }
    }

    private fun Project.configureAndroidExtension(
        android: LibraryExtension?,
        composeExtension: ComposeExtension?,
        versionCatalogs: VersionCatalogsExtension
    ) {
        if (android != null) {
            android.apply {
                val compileSdkVersion =
                    versionCatalogs.named("libs").findVersion("android-compileSdk").get().toString()
                        .toIntOrNull()
                if (compileSdkVersion != null) {
                    compileSdk = compileSdkVersion
                    logger.info("Setting compileSdk to: $compileSdkVersion")
                } else {
                    logger.error("Failed to parse compileSdk version from version catalog")
                }
                defaultConfig {
                    minSdk = getIntProperty("android.min.sdk")
                    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                    testInstrumentationRunnerArguments.set(
                        "runnerBuilder",
                        "de.mannodermaus.junit5.AndroidJUnit5Builder"
                    )
                }
                packaging {
                    resources {
                        pickFirsts.add("META-INF/LICENSE.md")
                        pickFirsts.add("META-INF/LICENSE-notice.md")
                    }
                }
                buildTypes.getByName("release") {
                    isMinifyEnabled = false // shared 不能 minify, 否则构建 app 会失败
                    isShrinkResources = false
                    proguardFiles(
                        getDefaultProguardFile("proguard-android-optimize.txt"),
                        *sharedAndroidProguardRules(),
                    )
                }
                buildFeatures {
                    if (composeExtension != null) {
                        compose = true
                    }
                }
            }
        }
    }

    private fun Project.configureIosTesting() {
        if (enableIos) {
            tasks.register<Copy>("copyiOSTestResources") {
                from("src/commonTest/resources")
                into("build/bin/iosSimulatorArm64/debugTest/resources")
            }
            tasks.named("iosSimulatorArm64Test") {
                dependsOn("copyiOSTestResources")
            }
        }
    }


    fun Project.configureCompileSdk() {
        val versionCatalogs = extensions.getByType(VersionCatalogsExtension::class)
        val compileSdkVersion = versionCatalogs.named("libs")
            .findVersion("android-compileSdk")
            .get()
            .toString()
            .toIntOrNull()

        extensions.findByType(CommonExtension::class)?.apply {
            if (compileSdkVersion != null) {
                compileSdk = compileSdkVersion
                logger.info("Setting compileSdk to: $compileSdkVersion")
            } else {
                logger.error("Failed to parse compileSdk version from version catalog")
            }
        }
    }
}
